/*
 * Traverser.java July 2006
 *
 * Copyright (C) 2006, Niall Gallagher <niallg@users.sf.net>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package wx.xml.simpleframework.xml.core;

import wx.xml.simpleframework.xml.strategy.Type;
import wx.xml.simpleframework.xml.stream.InputNode;
import wx.xml.simpleframework.xml.stream.OutputNode;
import wx.xml.simpleframework.xml.stream.Style;

/**
 * The <code>Traverser</code> object is used to traverse the XML class
 * schema and either serialize or deserialize an object. This is the
 * root of all serialization and deserialization operations. It uses
 * the <code>Root</code> annotation to ensure that the XML schema
 * matches the provided XML element. If no root element is defined the
 * serialization and deserialization cannot be performed.
 *
 * @author Niall Gallagher
 */
class Traverser {

    /**
     * This is the context object used for the traversal performed.
     */
    private final Context context;

    /**
     * This is the style that is used to style the XML roots.
     */
    private final Style style;

    /**
     * Constructor for the <code>Traverser</code> object. This creates
     * a traverser that can be used to perform serialization or
     * or deserialization of an object. This requires a source object.
     *
     * @param context the context object used for the traversal
     */
    public Traverser(Context context) {
        this.style = context.getStyle();
        this.context = context;
    }

    /**
     * This will acquire the <code>Decorator</code> for the type.
     * A decorator is an object that adds various details to the
     * node without changing the overall structure of the node. For
     * example comments and namespaces can be added to the node with
     * a decorator as they do not affect the deserialization.
     *
     * @param type this is the type to acquire the decorator for
     * @return this returns the decorator associated with this
     */
    private Decorator getDecorator(Class type) throws Exception {
        return context.getDecorator(type);
    }

    /**
     * This <code>read</code> method is used to deserialize an object
     * from the provided XML element. The class provided acts as the
     * XML schema definition used to control the deserialization. If
     * the XML schema does not have a <code>Root</code> annotation
     * this throws an exception. Also if the root annotation name is
     * not the same as the XML element name an exception is thrown.
     *
     * @param node this is the node that is to be deserialized
     * @param type this is the XML schema class to be used
     * @return an object deserialized from the XML element
     * @throws Exception if the XML schema does not match the node
     */
    public Object read(InputNode node, Class type) throws Exception {
        Composite factory = getComposite(type);
        Object    value   = factory.read(node);

        if (value != null) {
            Class real = value.getClass();

            return read(node, real, value);
        }
        return null;
    }

    /**
     * This <code>read</code> method will read the contents of the XML
     * document from the provided source and populate the object with
     * the values deserialized. This is used as a means of injecting an
     * object with values deserialized from an XML document. If the
     * XML source cannot be deserialized or there is a problem building
     * the object graph an exception is thrown.
     *
     * @param node  this is the node that is to be deserialized
     * @param value this is the value that is to be deserialized
     * @return an object deserialized from the XML element
     * @throws Exception if the XML schema does not match the node
     */
    public Object read(InputNode node, Object value) throws Exception {
        Class     type    = value.getClass();
        Composite factory = getComposite(type);
        Object    real    = factory.read(node, value);

        return read(node, type, real);
    }

    /**
     * This <code>read</code> method is used to deserialize an object
     * from the provided XML element. The class provided acts as the
     * XML schema definition used to control the deserialization. If
     * the XML schema does not have a <code>Root</code> annotation
     * this throws an exception. Also if the root annotation name is
     * not the same as the XML element name an exception is thrown.
     *
     * @param node  this is the node that is to be deserialized
     * @param value this is the XML schema object to be used
     * @return an object deserialized from the XML element
     * @throws Exception if the XML schema does not match the XML
     */
    private Object read(InputNode node, Class type, Object value) throws Exception {
        String root = getName(type);

        if (root == null) {
            throw new RootException(node, "Root annotation required for %s", type);
        }
        return value;
    }

    /**
     * This <code>validate</code> method will validate the contents of
     * the XML document against the specified XML class schema. This is
     * used to perform a read traversal of the class schema such that
     * the document can be tested against it. This is preferred to
     * reading the document as it does not instantiate the objects or
     * invoke any callback methods, thus making it a safe validation.
     *
     * @param type this is the class type to be validated against XML
     * @param node this provides the source of the XML document
     * @return true if the document matches the class XML schema
     * @throws Exception if the class XML schema does not fully match
     */
    public boolean validate(InputNode node, Class type) throws Exception {
        Composite factory = getComposite(type);
        String    root    = getName(type);

        if (root == null) {
            throw new RootException(node, "Root annotation required for %s", type);
        }
        return factory.validate(node);
    }

    /**
     * This <code>write</code> method is used to convert the provided
     * object to an XML element. This creates a child node from the
     * given <code>OutputNode</code> object. Once this child element
     * is created it is populated with the fields of the source object
     * in accordance with the XML schema class.
     *
     * @param source this is the object to be serialized to XML
     * @throws Exception thrown if there is a problem serializing
     */
    public void write(OutputNode node, Object source) throws Exception {
        write(node, source, source.getClass());
    }

    /**
     * This <code>write</code> method is used to convert the provided
     * object to an XML element. This creates a child node from the
     * given <code>OutputNode</code> object. Once this child element
     * is created it is populated with the fields of the source object
     * in accordance with the XML schema class.
     *
     * @param source this is the object to be serialized to XML
     * @param expect this is the class that is expected to be written
     * @throws Exception thrown if there is a problem serializing
     */
    public void write(OutputNode node, Object source, Class expect) throws Exception {
        Class  type = source.getClass();
        String root = getName(type);

        if (root == null) {
            throw new RootException(node, "Root annotation required for %s", type);
        }
        write(node, source, expect, root);
    }

    /**
     * This <code>write</code> method is used to convert the provided
     * object to an XML element. This creates a child node from the
     * given <code>OutputNode</code> object. Once this child element
     * is created it is populated with the fields of the source object
     * in accordance with the XML schema class.
     *
     * @param source this is the object to be serialized to XML
     * @param expect this is the class that is expected to be written
     * @param name   this is the name of the root annotation used
     * @throws Exception thrown if there is a problem serializing
     */
    public void write(OutputNode node, Object source, Class expect, String name) throws Exception {
        OutputNode child = node.getChild(name);
        Type       type  = getType(expect);

        if (source != null) {
            Class     actual    = source.getClass();
            Decorator decorator = getDecorator(actual);

            if (decorator != null) {
                decorator.decorate(child);
            }
            if (!context.setOverride(type, source, child)) {
                getComposite(actual).write(child, source);
            }
        }
        child.commit();
    }

    /**
     * This will create a <code>Composite</code> object using the XML
     * schema class provided. This makes use of the source object that
     * this traverser has been given to create a composite converter.
     *
     * @param expect this is the XML schema class to be used
     * @return a converter for the specified XML schema class
     */
    private Composite getComposite(Class expect) throws Exception {
        Type type = getType(expect);

        if (expect == null) {
            throw new RootException(this, "Can not instantiate null class");
        }
        return new Composite(context, type);
    }

    /**
     * This is used to acquire a type for the provided class. This will
     * wrap the class in a <code>Type</code> wrapper object. Wrapping
     * the class allows it to be used within the framework.
     *
     * @param type this is the type that is to be wrapped for use
     * @return this returns the type that wraps the specified class
     */
    private Type getType(Class type) {
        return new ClassType(type);
    }

    /**
     * Extracts the <code>Root</code> annotation from the provided XML
     * schema class. If no annotation exists in the provided class the
     * super class is checked and so on until the <code>Object</code>
     * is encountered, if no annotation is found this returns null.
     *
     * @param type this is the XML schema class to use
     * @return this returns the root annotation for the XML schema
     */
    protected String getName(Class type) throws Exception {
        String root = context.getName(type);
        String name = style.getElement(root);

        return name;
    }
}
